package org.geotools.geometry.iso.util.algorithm2D;

import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Stack;


/**
 * @author roehrig
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 *
 * @source $URL: http://svn.osgeo.org/geotools/tags/2.7-M3/modules/unsupported/geometry/src/main/java/org/geotools/geometry/iso/util/algorithm2D/AlgoLine2D.java $
 */
public class AlgoLine2D {

    public static final int EQUALSEGMENT = 1;
    public static final int OPPOSITESEGMENT = -1;
    public static final int DIFFERENTSEGMENT = 0;

    public static double length(Line2D line) {
        return line.getP1().distance(line.getP2());
    }
    
    public static ArrayList<Line2D> splitLines(double maxLength, ArrayList<Line2D> lines) {
        if (maxLength<=0.0) return lines;
        ArrayList<Line2D> result = new ArrayList<Line2D>(lines.size());
        for (int i = 0; i<lines.size();++i) {
            Line2D line = (Line2D)lines.get(i); 
            if (AlgoLine2D.length(line) > maxLength) {
                result.addAll(AlgoLine2D.split(line,maxLength));
            } else {
                result.add(line);
            }
                }
        return result;
    }

    /**
     * @param maxLength
     */
    public static ArrayList<Line2D> split(Line2D line, double maxSpacing) {
        ArrayList<Line2D> result = new ArrayList<Line2D>();
        int n = (int) Math.ceil(AlgoLine2D.length(line) / maxSpacing);
        double x1 = line.getX1();
        double y1 = line.getY1();
        double x2 = line.getX2();
        double y2 = line.getY2();
        double deltaX = (x2-x1) / n;
        double deltaY = (y2-y1) / n;
        Point2D p1 = line.getP1();
        for (int j = 1; j < n; ++j) {
            Point2D p2 = new Point2D.Double(x1 + deltaX * j, y1 + deltaY * j);
            result.add(new Line2D.Double(p1,p2));
            p1 = p2;
        }
        /** add the last node*/
        result.add(new Line2D.Double(p1,line.getP2()));
        return result;
    }
    
//  // first lines which are shorter as minLength will be merged
//  public static ArrayList mergeLines(double minLength, ArrayList lines) {
//      ArrayList result = new ArrayList(lines.size());
//      int i = 0;
//      int j = 0;
//      while(i < lines.size()){
//          Line2D line = (Line2D)lines.get(i);
//          if (line == lines.get(lines.size()-1)&& (AlgoLine.length(line) < minLength)&& (line.getP1().lineSize()< 3)){
//              //line= line.merge(result.get(result.size()-1));
//              line= (result.get(result.size()-1)).merge(line);
//              result.remove(result.size()-1);
//              result.add(line);
//              i++;
//          }
//          else if(AlgoLine.length(line) < minLength && (line.getP2().lineSize()< 3)){
//              j=i+1;
//              while((AlgoLine.length(line) < minLength) && (j<=(lines.size()-1))&&  (((AlgoLine)lines.get(j)).getP1().lineSize()< 3)){
//                  line = line.merge((AlgoLine)lines.get(j));
//                  j++;
//              }
//              i = j;
//              result.add(line);
//          }else {
//              result.add(line);
//              i += 1;
//          }
//      }       
//      return result;
//  }
    

    //  Ziethen 23.02.05 param minlengh
    //  first lines witch are shorter as minLength will be merged
    public static Line2D merge(Line2D line, Line2D other) {
        Line2D.Double result = new Line2D.Double(line.getP1(),other.getP2());
        return result;
    }
    
    
    
    public static  boolean isParallel(Line2D l0, Line2D l1){ 
        return AlgoLine2D.isParallel(l0.getP1(), l0.getP2(), l1.getP1(), l1.getP2());
    }
    
    public static boolean isParallel(Point2D p0, Point2D p1, Point2D q0, Point2D q1) {
        return Math.abs((double)AlgoPoint2D.cross(AlgoPoint2D.subtract(p1,p0),AlgoPoint2D.subtract(q1,q0))) <= AlgoPoint2D.EPSILON;
    }
    
    public  static double constrParamForPoint(Point2D p0 , Point2D p1 , Point2D dp) {
        // return the construction parametric coordinate (0.0 <= result <= 1.0) of
        // dp on the line (p0,p1)
        // if p0.equals(p1) then: if dp.equals(p0) return 0.0, else Double.NaN
        // if dp is close to p0 then result = 0.0
        // if dp is close to p1 then result = 1.0
        // if the line (p0,p1) does not contain dp then return Double.NaN
        double eps = AlgoPoint2D.EPSILON;
        double result = java.lang.Double.NaN;
        // return nothing if dp is not on the same line as (p0,p1): colinear
        if (!AlgoLine2D.isParallel(p0, p1, p0, dp)) return java.lang.Double.NaN;
        if (p0.equals(p1)) {
            if (AlgoPoint2D.equals(p0,dp,eps)) 
                return 0.0;
            else
                return java.lang.Double.NaN;   
        }
        if (Math.abs(p0.getX() - p1.getX()) > Math.abs(p0.getY() - p1.getY()))
            result = (dp.getX() - p0.getX()) / (p1.getX() - p0.getX());
        else
            result = (dp.getY() - p0.getY()) / (p1.getY() - p0.getY());
        if (Math.abs(result) <= eps) result = 0.0;
        if (Math.abs(1.0 - result) <= eps) result = 1.0;
        if (result < 0.0 || result > 1.0) return java.lang.Double.NaN;
        return result;
    }

    public static double orientation(Line2D line,Point2D point) {
        //(ZA)21.12.04 returns a Value about a Curve-Orientation (clockwise or counterclockwise)
            return AlgoPoint2D.cross((AlgoPoint2D.subtract(point,line.getP1())),
                                    AlgoPoint2D.subtract((line.getP2()),line.getP1()));
        
        }
    /**
     * @param dp
     * @return
     */
    public static boolean rightSide(Line2D line, Point2D p) {
        /**
         * A return value of 1 relativeCCW indicates that the line segment must
         * turn in the direction that takes the positive X axis towards the
         * negative Y axis. In the default coordinate system used by Java 2D,
         * this direction is counterclockwise, in our coordinate system it is
         * clockwise (right side)
         * 
         */
        return rightSide(line.getP1(),line.getP2(),p);
    }

    public static boolean rightSide(Point2D p0, Point2D p1, Point2D p) {
        /**
         * A return value of 1 relativeCCW indicates that the line segment must
         * turn in the direction that takes the positive X axis towards the
         * negative Y axis. In the default coordinate system used by Java 2D,
         * this direction is counterclockwise, in our coordinate system it is
         * clockwise (right side)
         * 
         */
        return AlgoPoint2D.cross(AlgoPoint2D.subtract(p,p0),AlgoPoint2D.subtract(p1,p0)) >  0.0;
    }

    public static boolean leftSide(Line2D line, Point2D p) {
        return leftSide(line.getP1(),line.getP2(),p);
        //return this.relativeCCW(p) == -1;
    }

    public static boolean leftSide(Point2D p0, Point2D p1, Point2D p) {
        return AlgoPoint2D.cross(AlgoPoint2D.subtract(p,p0),AlgoPoint2D.subtract(p1,p0)) <  0.0;
        //return this.relativeCCW(p) == -1;
    }

    /**
     * Returns:
     * 0 if it is not the same segment, i.e !equal AND !inverted
     * 1 if it is equal,    i.e. pa0.equals(pb0) && pa1.equals(pb1)
     * 2 if it is inverted, i.e. pa0.equals(pb1) && pa1.equals(pb0)
     * @return 
     */
    public static int sameSegment(Line2D lineA, Line2D lineB) {
        return sameSegment(lineA.getP1(), lineA.getP2(), lineB.getP1(), lineB.getP2());
    }
    
    /**
     * Returns:
     * 0 if it is not the same segment, i.e !equal AND !inverted
     * 1 if it is equal,    i.e. pa0.equals(pb0) && pa1.equals(pb1)
     * 2 if it is inverted, i.e. pa0.equals(pb1) && pa1.equals(pb0)
     * @return 
     */
    public static int sameSegment(Line2D line,Point2D pb0, Point2D pb1) {
        return sameSegment(line.getP1(), line.getP2(), pb0, pb1);
    }
    
    /**
     * Returns:
     * 0 if it is not the same segment, i.e !equal AND !inverted
     * 1 if it is equal,    i.e. pa0.equals(pb0) && pa1.equals(pb1)
     * 2 if it is inverted, i.e. pa0.equals(pb1) && pa1.equals(pb0)
     * @param pa0
     * @param pa1
     * @param pb0
     * @param pb1
     * @return 
     */
    public static int sameSegment(Point2D pa0, Point2D pa1, Point2D pb0, Point2D pb1) {
        return (pa0.equals(pb0) && pa1.equals(pb1)) ? EQUALSEGMENT : (pa0.equals(pb1) && pa1.equals(pb0) ? OPPOSITESEGMENT : DIFFERENTSEGMENT);
    }
    
    public static Point2D evaluate( Line2D line, double r) {
        return AlgoPoint2D.evaluate(line.getP1(),line.getP2(),r);
    }

    public static GeneralPath reverse(GeneralPath path) {
        // JR uncomplete
        PathIterator pi = path.getPathIterator(new AffineTransform());
        double[] coords = new double[6];
        Stack<Point2D> ps = new Stack<Point2D>();
        while (!pi.isDone()) {
            int type = pi.currentSegment(coords);
            if ((type == PathIterator.SEG_MOVETO)
                    || (type == PathIterator.SEG_LINETO)) {
                ps.push(new Point2D.Double(coords[0], coords[1]));
            }
            pi.next();
        }
        GeneralPath revPath = new GeneralPath();
        Point2D.Double p = (Point2D.Double)ps.pop();
        revPath.moveTo((float) p.x, (float) p.y);
        while (!ps.empty()) {
            revPath.lineTo((float) p.x, (float) p.y);
        }
        return revPath;
    }
    
    public static double getAngle2D(Line2D line, Point2D point) {
        //             * p1
        //            /
        //           /    
        //          /
        //         *------>*
        //       (0,0)    this
        return AlgoPoint2D.getAngle2D(AlgoPoint2D.subtract(line.getP2(),line.getP1()), point);
    }
}